package oop.ex3.nameserver;

import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import oop.ex3.common.CommunicationSession;
import oop.ex3.common.Protocol;
import oop.ex3.common.SocketCommunicationSession;

public class SessionThread extends Thread{
	private SocketCommunicationSession _session;
	private NameServerDataBase _myDataBase;
	private boolean _wasCommandChecked;
	private boolean _isGoAwayCommand;
	
	public SessionThread(Socket socket,NameServerDataBase database){
		try {
			_session = new SocketCommunicationSession(socket);
			_myDataBase = database;
			_wasCommandChecked = false;
			_isGoAwayCommand = false;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
	
	
	@Override
	public void run(){
	try {
		//reading begin:
		_session.readString();
		String host = _session.readString();
		int port = _session.readInt();
		//reading end:
		_session.readString();	
		FileManager fileManager = _myDataBase.identifyFileManager(host, port);
		if(fileManager==null){
			_wasCommandChecked = true;
			handleNewClient(host, port);
		}else{
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
			String message = _session.readString();
			if(message.equals(Protocol.MSG_GOODBYE)){
				_wasCommandChecked = true;
				//reading end:
				_session.readString();
				handleGoodbey(fileManager);
			}else if(message.equals(Protocol.MSG_DONT_CONTAIN_FILE)){
				_wasCommandChecked = true;
				String fileToRemove = _session.readString();
				//reading end:
				_session.readString();
				handleFileDeletion(fileManager, fileToRemove);
			}else if(message.equals(Protocol.MSG_CONTAIN_FILE)){
				_wasCommandChecked = true;
				String newFile = _session.readString();
				//reading end:
				_session.readString();
				handleFileAddition(fileManager, newFile);
			}else if(message.equals(Protocol.MSG_WANT_FILE)){
				_wasCommandChecked = true;
				String wantedFile = _session.readString();
				//reading end:
				_session.readString();
				handleWantedFile(wantedFile);
			}else if(message.equals(Protocol.MSG_WANT_SERVERS)){
				_wasCommandChecked = true;
				//reading end:
				_session.readString();
				handleWantServers();
			}else if(message.equals(Protocol.MSG_WANT_ALL_FILES)){
				_wasCommandChecked = true;
				//reading end:
				_session.readString();
				handleWantAllFiles();
			}else if(message.equals(Protocol.MSG_GO_AWAY)){
				//reading end:
				_session.readString();	
				handleGoAway();
				_wasCommandChecked = true;
				}
			}
	} catch (IOException e) {
		sendErrorAndCloseConnection(_session);
	}

	}

	
	private void handleGoAway() {
		_isGoAwayCommand = true;
		try {
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
			_session.readString();
			_session.readString();
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
		} catch (IOException e) {
			sendErrorAndCloseConnection(_session);
		}
	}


	private void handleWantAllFiles() {
		Set<String> allFiles = _myDataBase.getAllKnownFiles();
			try {
				if(allFiles.size()==0){
					_session.sendString(Protocol.MSG_NO_KNOWN_FILES);
					_session.sendString(Protocol.MSG_END);
					_session.readString();
					_session.readString();
					_session.sendString(Protocol.MSG_DONE);
					_session.sendString(Protocol.MSG_END);

				}else{
					for (String fileName : allFiles){
						_session.sendString(Protocol.MSG_NS_CONTAINS_FILE);
						_session.sendString(fileName);
						_session.sendString(Protocol.MSG_END);
					}
					_session.sendString(Protocol.MSG_END_LIST);
					_session.sendString(Protocol.MSG_END);
					_session.readString();
					_session.readString();
					_session.sendString(Protocol.MSG_DONE);
					_session.sendString(Protocol.MSG_END);
				}	
			} catch (IOException e) {
				sendErrorAndCloseConnection(_session);
			}
		}
		
		
	


	private void handleWantServers() {
		List<RemoteNameServer> knownNameServers = _myDataBase.getKnownNameServers();
			try {
				for(RemoteNameServer nameServer : knownNameServers){
					_session.sendString(Protocol.MSG_CONTAIN_NS);
					_session.sendString(nameServer.getHost());
					_session.sendInt(nameServer.getPort());
					_session.sendString(Protocol.MSG_END);
				}
				_session.sendString(Protocol.MSG_END_LIST);
				_session.sendString(Protocol.MSG_END);
				_session.readString();
				_session.readString();
				_session.sendString(Protocol.MSG_DONE);
				_session.sendString(Protocol.MSG_END);
				
			} catch (IOException e) {
				sendErrorAndCloseConnection(_session);
			}
		}



	private void handleWantedFile(String wantedFile) {
		ArrayList<FileManager> managersWithFile = _myDataBase.getFileManagersWithFile(wantedFile);
		try {
			if(managersWithFile==null){
				_session.sendString(Protocol.MSG_FILE_NOT_FOUND);
				_session.sendString(Protocol.MSG_END);
				_session.readString();
				_session.readString();
				_session.sendString(Protocol.MSG_DONE);
				_session.sendString(Protocol.MSG_END);
			}else{
				for(FileManager manager : managersWithFile){
					_session.sendString(Protocol.MSG_FILE_ADDRESS);
					_session.sendString(manager.getHost());
					_session.sendInt(manager.getPort());
					_session.sendString(Protocol.MSG_END);
				}
				_session.sendString(Protocol.MSG_END_LIST);
				_session.sendString(Protocol.MSG_END);
				_session.readString();
				_session.readString();
				_session.sendString(Protocol.MSG_DONE);
				_session.sendString(Protocol.MSG_END);
			}
			} catch (IOException e) {
				sendErrorAndCloseConnection(_session);
			}
		}
	


	private void handleFileAddition(FileManager fileManager, String newFile) {
		_myDataBase.addFile(fileManager, newFile);
		try {
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
			_session.readString();
			_session.readString();
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
		} catch (IOException e) {
			sendErrorAndCloseConnection(_session);
		}
	}


	private void handleFileDeletion(FileManager fileManager, String fileToRemove) {
		_myDataBase.removeFile(fileManager, fileToRemove);
		try {
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
			_session.readString();
			_session.readString();
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
		} catch (IOException e) {
			sendErrorAndCloseConnection(_session);
		}
	}


	private void handleGoodbey(FileManager fileManager) {
		_myDataBase.removeFileManager(fileManager);	
		//sending done and end:
		try {
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
			_session.readString();
			_session.readString();
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
		} catch (IOException e) {
			sendErrorAndCloseConnection(_session);
		}
	}


	private void handleNewClient(
			String host, int port) {
		List<String> filesOfThisFileManager = new ArrayList<String>();
		List<RemoteNameServer> nameServersOfThisFileManager = new ArrayList<RemoteNameServer>();
		try {
			_session.sendString(Protocol.MSG_WELCOME);
			_session.sendString(Protocol.MSG_END);
			String message = _session.readString();
			if(message.equals(Protocol.MSG_CONTAIN_FILE)){
				String fileName;
				while(!message.equals(Protocol.MSG_END_LIST)){
					fileName = _session.readString();
					filesOfThisFileManager.add(fileName);
					//reading end:
					_session.readString();
					//sending done and end:
					_session.sendString(Protocol.MSG_DONE);
					_session.sendString(Protocol.MSG_END);
					message = _session.readString();
				}
			}else if (message.equals(Protocol.MSG_END_LIST)){
				//reading end:
				_session.readString();
				//sending done and end:
				_session.sendString(Protocol.MSG_DONE);
				_session.sendString(Protocol.MSG_END);
			}
			//reading end:
			_session.readString();
			//sending done and end:
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);

			
			message = _session.readString();
			if(message.equals(Protocol.MSG_CONTAIN_NS)){
				String nameServerHost;
				int nameServerPort;
				RemoteNameServer newRemoteNameServer;
				while(!message.equals(Protocol.MSG_END_LIST)){
					nameServerHost = _session.readString();
					nameServerPort = _session.readInt();
					//reading end:
					_session.readString();
					
					newRemoteNameServer = new RemoteNameServer(nameServerHost, nameServerPort);
					nameServersOfThisFileManager.add(newRemoteNameServer);
				
					//sending done and end:
					_session.sendString(Protocol.MSG_DONE);
					_session.sendString(Protocol.MSG_END);
					message = _session.readString();
				}
			}else if (message.equals(Protocol.MSG_END_LIST)){
				//reading end:
				_session.readString();
				//sending done and end:
				_session.sendString(Protocol.MSG_DONE);
				_session.sendString(Protocol.MSG_END);
			}
			//reading end:
			_session.readString();
			//sending done and end:
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
			//reading endSession end:
			_session.readString();
			_session.readString();
			//sending done and end:
			_session.sendString(Protocol.MSG_DONE);
			_session.sendString(Protocol.MSG_END);
		
			FileManager newClient = new FileManager(host, port);
			_myDataBase.addNewFileManager(newClient, filesOfThisFileManager, nameServersOfThisFileManager);
		} catch (IOException e) {
			sendErrorAndCloseConnection(_session);
		}
		
	}


	public boolean isGoAwayCommand() {
		while(!_wasCommandChecked){
			//do nothing, wait.
		}
		return _isGoAwayCommand;
	}


	private void sendErrorAndCloseConnection(SocketCommunicationSession session){
		try {
			session.sendString(Protocol.MSG_ERROR);
			session.sendString(Protocol.MSG_END);
			session.close();
		} catch (IOException e) {
			sendErrorAndCloseConnection(_session);
		}
	}

}
